package apobates.gui.formatter.richedit;

import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.concurrent.Task;
import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;
import org.fxmisc.flowless.VirtualizedScrollPane;
import org.fxmisc.richtext.CodeArea;
import org.fxmisc.richtext.GenericStyledArea;
import org.fxmisc.richtext.LineNumberFactory;
import org.fxmisc.richtext.model.Paragraph;
import org.fxmisc.richtext.model.StyleSpans;
import org.fxmisc.richtext.model.StyleSpansBuilder;
import org.reactfx.collection.ListModification;
import java.time.Duration;
import java.util.Collection;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
import java.util.function.Function;
/**
 * 右侧的区域(已格式化内容)编辑器
 */
public class CodeAreaHelper {
    private ExecutorService executor;

    /**
     * 初始化
     * @param codeArea
     */
    public void initializeEditor(CodeArea codeArea, SimpleStringProperty currentClass) {
        codeArea.setParagraphGraphicFactory(LineNumberFactory.get(codeArea));
        // 异步模式
        executor = Executors.newSingleThreadExecutor();
        System.out.println("[CAH][Async]创建执行器");
        codeArea.multiPlainChanges()
                .successionEnds(Duration.ofMillis(500))
                .retainLatestUntilLater(executor)
                .supplyTask(()->computeHighlightingAsync(codeArea))
                .awaitLatest(codeArea.multiPlainChanges())
                .filterMap(t -> {
                    if(t.isSuccess()) {
                        return Optional.ofNullable(t.get());
                    } else {
                        t.getFailure().printStackTrace();
                        return Optional.empty();
                    }
                })
                .subscribe((StyleSpans<Collection<String>> highlighting)->applyHighlighting(highlighting,codeArea));
        //
        final AnchorPane parent = (AnchorPane) codeArea.getParent();
        VirtualizedScrollPane<CodeArea> vsPane = new VirtualizedScrollPane<>(codeArea);
        parent.getChildren().add(vsPane);
        //
        codeArea.getStylesheets().add(getClass().getResource("/css/json-lexical.css").toExternalForm());
        // 过渡提示
        final Label placeholderLabel = new Label();
        placeholderLabel.textProperty()
                .bind(Bindings.when(currentClass.isEmpty())
                        .then("格式化后的内容在此显示")
                        .otherwise("处理中..."));
        codeArea.setPlaceholder(placeholderLabel);
        codeArea.mouseTransparentProperty().bind(Bindings.createBooleanBinding(() -> codeArea.getText().isEmpty(), codeArea.textProperty()));
        codeArea.focusTraversableProperty().bind(Bindings.createBooleanBinding(() -> !codeArea.getText().isEmpty(), codeArea.textProperty()));
    }
    // 异步模式时生成执行任务
    private Task<StyleSpans<Collection<String>>> computeHighlightingAsync(CodeArea codeArea){
        System.out.println("[CAH][Async]异步SP执行任务");
        String text = codeArea.getText();
        Task<StyleSpans<Collection<String>>> task = new Task<StyleSpans<Collection<String>>>() {
            @Override
            protected StyleSpans<Collection<String>> call() throws Exception {
                try {
                    return computeJsonHighlighting(text);
                }catch (IllegalStateException e){
                    return null; //new StyleSpansBuilder<Collection<String>>().create();
                }
            }
        };
        executor.execute(task);
        return task;
    }
    // 异步模式时应用样式SP
    private void applyHighlighting(StyleSpans<Collection<String>> highlighting, CodeArea codeArea) {
        System.out.println("[CAH][Async]为CodeArea 设置SP");
        if(null == highlighting){
            codeArea.clear();
        }else {
            codeArea.setStyleSpans(0, highlighting);
        }
    }
    // 异步模式时关闭执行器
    public void stop() {
        System.out.println("[CAH][Async]关闭执行器");
        executor.shutdown();
    }
    /**
     * 设置字体字号
     * @param codeArea
     * @param family
     * @param fontSize
     */
    public void setCodeAreaFont(CodeArea codeArea, String family, Double fontSize){
        codeArea.setStyle("-fx-font-size: "+fontSize+"; -fx-font-family:'"+family+"'");
    }
    public static StyleSpans<Collection<String>> computeJsonHighlighting(String text){
        // 基于正则的匹配
        return new JsonKeyWordRegexMatch().computeHighlighting(text);
    }
    public static class VisibleParagraphStyler<PS, SEG, S> implements Consumer<ListModification<? extends Paragraph<PS, SEG, S>>>
    {
        private final GenericStyledArea<PS, SEG, S> area;
        private final Function<String, StyleSpans<S>> computeStyles;
        private int prevParagraph, prevTextLength;

        public VisibleParagraphStyler( GenericStyledArea<PS, SEG, S> area, Function<String,StyleSpans<S>> computeStyles )
        {
            this.computeStyles = computeStyles;
            this.area = area;
        }

        @Override
        public void accept( ListModification<? extends Paragraph<PS, SEG, S>> lm )
        {
            if ( lm.getAddedSize() > 0 ) Platform.runLater( () ->
            {
                int paragraph = Math.min( area.firstVisibleParToAllParIndex() + lm.getFrom(), area.getParagraphs().size()-1 );
                String text = area.getText( paragraph, 0, paragraph, area.getParagraphLength( paragraph ) );

                if ( paragraph != prevParagraph || text.length() != prevTextLength )
                {
                    if ( paragraph < area.getParagraphs().size()-1 )
                    {
                        int startPos = area.getAbsolutePosition( paragraph, 0 );

                        area.setStyleSpans(startPos, computeStyles.apply(text));
                    }
                    prevTextLength = text.length();
                    prevParagraph = paragraph;
                }
            });
        }
    }
}
